package edu.fiu.cis.personalize.server.objectrank;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import edu.fiu.cis.personalize.server.GraphMediator;
import edu.fiu.cis.personalize.server.graph.Document;
import edu.fiu.cis.personalize.server.graph.Node;
import edu.fiu.cis.personalize.server.graph.NodeType;
import edu.fiu.cis.personalize.server.graph.Query;
import edu.fiu.cis.personalize.server.graph.store.Edges;
import edu.fiu.cis.personalize.server.graph.update.GraphUpdateFactory;
import edu.fiu.cis.personalize.server.util.PajekFileGenerator;
import edu.fiu.cis.personalize.shared.GoogleDocument;

public class PersonalizedObjectRank {

	private HashMap<NodeType, HashMap<Integer, NodeRank>> rankCache = new HashMap<NodeType, HashMap<Integer, NodeRank>>();

	private int iteration = 0;

	private static final double IMPACT_FACTOR = 0.5;
	private static final double DAMPENING_FACTOR = 0.85;
	private static final double THRESHOLD = 0.001;

	private Set<NodeRef> baseSet;

	public PersonalizedObjectRank(Set<NodeRef> baseset) {
		this.baseSet = baseset;
		rankCache.put(NodeType.DOCUMENT, new HashMap<Integer, NodeRank>());
		rankCache.put(NodeType.QUERY, new HashMap<Integer, NodeRank>());
		rankCache.put(NodeType.USER, new HashMap<Integer, NodeRank>());
		rankCache.put(NodeType.GROUP, new HashMap<Integer, NodeRank>());
	}

	public void process() {

		ArrayList<NodeRef> allNodes = GraphMediator.get().getAllNodeRefs();

		for (NodeRef r : allNodes) {

			NodeRank nr = new NodeRank(r, 1); 
			rankCache.get(r.getNodeType()).put(r.getId(), nr);
		}

		while (isThresholdSatisfied()) {
			processIteration();
			iteration++;
		}
	}

	private boolean isThresholdSatisfied() {
		for (HashMap<Integer, NodeRank> m : rankCache.values()) {
			for (NodeRank r : m.values()) {
				if (r.getDelta() > THRESHOLD) {
					return false;
				}
			}
		}
		return true;
	}

	public ArrayList<GoogleDocument> updateGoogleResult(Query q,
			List<GoogleDocument> list) {

		double step = 0;
		if (!rankCache.get(NodeType.DOCUMENT).isEmpty()) {
			double minR = Double.MAX_VALUE;
			double maxR = Double.MIN_VALUE;
			for (NodeRank nr : rankCache.get(NodeType.DOCUMENT).values()) {
				if (nr.getRank() < minR) {
					minR = nr.getRank();
				}
				if (nr.getRank() > maxR) {
					maxR = nr.getRank();
				}
			}
			step = (minR + maxR) / list.size();
		}

		int idx = list.size();

		final HashMap<Integer, Double> objectRank = new HashMap<Integer, Double>();
		final HashMap<Integer, Integer> googleRank = new HashMap<Integer, Integer>();

		for (GoogleDocument gd : list) {
			Document d = (Document) GraphMediator.get().get(gd.getURL(),
					NodeType.DOCUMENT, true);
			d.setContent(gd.getContent());
			d.setTitle(gd.getTitle());
			gd.setId(d.getId());
			double nodeRank = idx-- * step * (1 - IMPACT_FACTOR);
			objectRank.put(d.getId(), nodeRank);
			googleRank.put(d.getId(), list.size() - idx);
			if (d.isNew()) {
				rankCache.get(NodeType.DOCUMENT).put(
						d.getId(),
						new NodeRank(new NodeRef(d.getId(), NodeType.DOCUMENT),
								0));
			}

			GraphMediator.performDocumentQueryAssociation(q, d);
		}

		ArrayList<NodeRank> nrs = new ArrayList<NodeRank>(rankCache.get(
				NodeType.DOCUMENT).values());

		for (NodeRank r : nrs) {
			Double rank = objectRank.get(r.getNodeRef().getId());
			if (rank == null) {
				rank = 0.0;
			}
			rank = rank + IMPACT_FACTOR * r.getRank();
			objectRank.put(r.getNodeRef().getId(), rank);
		}

		Collections.sort(nrs, new Comparator<NodeRank>() {

			@Override
			public int compare(NodeRank o1, NodeRank o2) {
				return objectRank.get(o2.getNodeRef().getId()).compareTo(
						objectRank.get(o1.getNodeRef().getId()));
			}
		});

		for (int i = 0; i < list.size(); i++) {
			GoogleDocument d = list.get(i);
			double or = (i + 1) * step * (1 - IMPACT_FACTOR);
			Node n = GraphMediator.get().get(d.getURL(), NodeType.DOCUMENT,
					false);
			NodeRank dr = null;
			if (n != null) {
				dr = rankCache.get(n.getType()).get(n.getId());
			}

			if (dr != null) {
				or += IMPACT_FACTOR * dr.getRank();
			}
			d.setObjectRank(or);
		}

		ArrayList<GoogleDocument> gdl = new ArrayList<GoogleDocument>();
		for (int i = 0; i < Math.min(nrs.size(), 15); i++) {
			NodeRank nr = nrs.get(i);
			Document d = (Document) GraphMediator.getNode(nr.getNodeRef()
					.getId(), nr.getNodeRef().getNodeType());
			GoogleDocument g = new GoogleDocument();
			gdl.add(g);
			g.setId(d.getId());
			g.setContent(d.getContent());
			g.setTitle(d.getTitle());
			g.setURL(d.getURL());
			g.setObjectRank(PajekFileGenerator.format(nr.getRank()));
			Integer grank = googleRank.get(d.getId());
			g.setGoogleRank(grank == null ? -1 : grank);
		}

		return gdl;
	}

	private void processIteration() {

		for (Entry<NodeType, HashMap<Integer, NodeRank>> allE : rankCache
				.entrySet()) {
			for (NodeRank rx : allE.getValue().values()) {
				double rrank = 0;
				Node rnode = GraphMediator.getNode(rx.getNodeRef().getId(), rx
						.getNodeRef().getNodeType());
				for (NodeType ytype : NodeType.values()) {
					double rcomp = 0;
					Edges docEdges = rnode.getEdges().getEdgeMap(ytype);
					Map<Integer, Double> m = docEdges.getInLinksMap();
					for (Entry<Integer, Double> e : m.entrySet()) {
						if (isValidNode(e.getKey(), ytype)) {
							rcomp += computeWeight(rnode, e.getKey(), ytype);
						}
					}
					rrank += rcomp;
				}
				rrank = rrank * DAMPENING_FACTOR + (1 - DAMPENING_FACTOR)
						* (baseSet.contains(rx.getNodeRef()) ? 0.5 : 0);
				rx.setRank(rrank);
			}
		}
	}

	private double computeWeight(Node x, Integer yId, NodeType ytype) {
		double r = rankCache.get(ytype).get(yId).getRank();

		Node y = GraphMediator.getNode(yId, ytype);

		r *= GraphUpdateFactory.getAuthorityTransferRate(y, x);

		r *= 1 / (1.0 * y.getOutDegree(x.getType()));

		r *= y.getEdges().getOutLinkWeight(x);

		return r;
	}

	private boolean isValidNode(Integer key, NodeType type) {
		return rankCache.get(type).containsKey(key);
	}
}
